<?php
/**
 *
 * Reflection-based server introspection.
 *
 * Developed for, and then donated by, Mashery.com <http://mashery.com>.
 *
 * @category Tipos
 *
 * @package Sungrazr_Controller_Server
 *
 * @subpackage Sungrazr_Controller_Server_Api
 *
 * @author Clay Loveless <clay@killersoft.com>
 *
 * @license http://opensource.org/licenses/bsd-license.php BSD
 *
 * @version SVN: $Id: Api.php 124 2008-03-10 08:36:17Z rodrigo.moraes $
 *
 */
class Sungrazr_Controller_Server_Api extends Solar_Docs_Apiref {

    /**
     *
     * User-provided configuration.
     *
     * Config keys are ...
     *
     * `phpdoc`
     * : (dependency) Config container for the Sungrazr_Docs_Phpdoc
     * dependency object.
     *
     * `cache`
     * : (array) Factory configuration for a Solar_Cache adapter.
     *
     * @var array
     *
     */
    protected $_Sungrazr_Controller_Server_Api = array(
        'phpdoc' => null,
        'cache' => array(
            'adapter'   => 'Solar_Cache_Adapter_File',
            'active'    => true,
            'life'      => 86400,
        ),
    );

    /**
     *
     * PHP's Magic Methods, these are ignored.
     *
     * @var array
     *
     */
    protected $_magic_methods = array(
        '__construct',
        '__destruct',
        '__get',
        '__set',
        '__call',
        '__sleep',
        '__wakeup',
        '__isset',
        '__unset',
        '__tostring',
        '__clone',
        '__set_state',
    );

    /**
     *
     * Cache keys generated by the _fetchCachedClass() method, which
     * are susequently used to add a parsed class to the cache.
     *
     * @param array
     *
     */
    protected $_class_cache_keys = array();

    /**
     *
     * Solar_Cache object for caching parsed class information.
     *
     * @param Solar_Cache_Adapter
     *
     */
    protected $_cache;

    /**
     *
     * Constructor.
     *
     * @param array $config User-provided configuration values.
     *
     */
    public function __construct($config = null)
    {
        // set the phpdoc parser to Sungrazr extension
        $this->_Sungrazr_Controller_Server_Api['phpdoc'] = Solar::factory(
            'Sungrazr_Docs_Phpdoc'
        );

        // basic construction
        parent::__construct($config);

        // set up class cache
        if (! empty($this->_config['cache'])) {
            $this->_cache = Solar::factory('Solar_Cache',
                $this->_config['cache']
            );
        }
    }

    /**
     *
     * Performs class/object reflection for server introspection.
     *
     * Introspection is performed by leveraging the reflection/introspection
     * capabilities found in Solar_Docs_Apiref. Since such introspection is
     * computationally expensive, aggressive caching (24 hours by default) is
     * done using the output of hash_file() on the class file as keys.
     *
     * @param mixed $class The class to add to the server API. If an object is
     * passed, the class will be picked out using get_class().
     *
     * @param string $namespace Optionally specify a namespace for the methods
     * of the added class. If empty, no namespace will be used. Default is
     * 'auto', which automatically sets the namespace as the lowercased final
     * segment of the class name.
     *
     * @return bool
     *
     */
    public function addClass($class, $namespace = 'auto')
    {
        if (is_object($class)) {
            $class = get_class($class);
        }

        // fetch from cache if possible
        if ($cached = $this->_fetchCachedClass($class)) {
            $this->api[$class] = $cached;
            return true;
        }

        $added = parent::addClass($class);

        if (! $added) {
            return false;
        }

        // drop Solar_Base descendants and magic methods
        foreach ($this->api[$class]['methods'] as $method => $info) {
            if ($info['from'] == 'Solar_Base' ||
                in_array($method, $this->_magic_methods)) {
                unset($this->api[$class]['methods'][$method]);
            }
        }

        // drop non-public properties, and properties from Solar_Base
        foreach ($this->api[$class]['properties'] as $property => $info) {
            if ($info['from'] == 'Solar_Base' || $info['access'] != 'public') {
                unset($this->api[$class]['properties'][$property]);
            }
        }

        // remap with namespace
        $ns = '';
        if (! empty($namespace)) {
            if ($namespace == 'auto') {
                $parts = explode('_', $class);
                $last = end($parts);
                $ns = strtolower($last) . '.';
            } else {
                $ns = (string) $namespace . '.';
            }
        }

        // populate server api
        $this->api[$class]['server_api'] = array();
        foreach ($this->api[$class]['methods'] as $method => $info) {
            if ($info['access'] == 'public') {
                $server_method = $ns . $method;
                // keep track of where method originated
                $info['callback'] = array($class, $method);
                $this->api[$class]['server_api'][$server_method] = $info;
            }
        }

        // cache result
        $this->_addCachedClass($class);

        // done!
        return true;
    }

    /**
     *
     * Fetch cached API reference output.
     *
     * Attempt to retrieve the Apiref-parsed array for the class. Since
     * parsing is expensive, avoid retrieving any more often than necessary
     * by using hash_file() to generate a cache key for the named class.
     *
     * This causes us to hit the disk, but is less expensive than the full
     * Apiref-parsing operation w/reflection.
     *
     * @param string $class Class name to retrieve from cache.
     *
     */
    protected function _fetchCachedClass($class)
    {
        // nothing to do if a cache adapter is not configured
        if (empty($this->_cache)) {
            return false;
        }

        $path = str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
        if ($full_path = Solar_File::exists($path)) {
            // get hash of file in this location
            $this->_class_cache_keys[$class] = hash_file('md5', $full_path);
        }

        return $this->_cache->fetch($this->_class_cache_keys[$class]);
    }

    /**
     *
     * Add a class API reference to the cache.
     *
     * @param string $class Name of class API to cache.
     *
     */
    protected function _addCachedClass($class)
    {
        // nothing to do if a cache adapter is not configured
        if (empty($this->_cache)) {
            return false;
        }

        return $this->_cache->add(
            $this->_class_cache_keys[$class],
            $this->api[$class]
        );
    }
}